package beastick.security.generic;

//Copyright 2008 Tofig Kareemov, estereos@gmail.com
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.

import java.net.InetAddress;
import java.rmi.dgc.VMID;
import beastick.utility.CUtilityMethods;

public class SecureRandom extends CUtilityMethods
{
  public static final int    I_DOUBLELONG_BYTE_LENGTH;
  public static final int    I_DOUBLEINT_BYTE_LENGTH;
  public static final int    I_DOUBLESHORT_BYTE_LENGTH;
  public static final int    I_LONG_BYTE_LENGTH;
  public static final int    I_INT_BYTE_LENGTH;
  public static final int    I_SHORT_BYTE_LENGTH;
  public static final int    I_UNIQUE_BYTE_ARRAY_LENGTH;
  public static final String sMachineID;
  public static final byte[] iMachineIDByteArray;
  public static final double rPHI;
  private static final int   I_SEED_SIZE_MAX = 257;
  private static final int   I_SEED_SIZE_MIN = 3;
  private static int         iScatterGlue;
  private static int         iUniqueIteration;
  static
  {
    String lsMachineID = "";
    long iMax;
    int iSize;
    double rPHINext = 1;
    double rPHIPrev = 0;
    for (int i = 0; (rPHINext != rPHIPrev) && (i < 1000); ++i)
    {
      rPHIPrev = rPHINext;
      rPHINext = 1 / (1 + rPHINext);
    }
    rPHI = rPHINext;
    iScatterGlue = 0x5A;
    iUniqueIteration = 0;
    try
    {
      lsMachineID = InetAddress.getLocalHost().toString() + "/"
        + SecureRandom.class.getName();
    }
    catch (Throwable e)
    {
      lsMachineID = (new VMID()).toString() + "/"
        + SecureRandom.class.getName();
    }
    sMachineID = lsMachineID + rPHI;
    iMachineIDByteArray = sMachineID.getBytes();
    iMax = Long.MAX_VALUE;
    iSize = 0;
    for (iSize = 0; iMax > 0; ++iSize)
    {
      iMax = iMax / 256;
    }
    I_LONG_BYTE_LENGTH = iSize;
    I_DOUBLELONG_BYTE_LENGTH = I_LONG_BYTE_LENGTH * 2;
    iMax = Integer.MAX_VALUE;
    iSize = 0;
    for (iSize = 0; iMax > 0; ++iSize)
    {
      iMax = iMax / 256;
    }
    I_INT_BYTE_LENGTH = iSize;
    I_DOUBLEINT_BYTE_LENGTH = I_INT_BYTE_LENGTH * 2;
    iMax = Short.MAX_VALUE;
    iSize = 0;
    for (iSize = 0; iMax > 0; ++iSize)
    {
      iMax = iMax / 256;
    }
    I_SHORT_BYTE_LENGTH = iSize;
    I_DOUBLESHORT_BYTE_LENGTH = I_SHORT_BYTE_LENGTH * 2;
    I_UNIQUE_BYTE_ARRAY_LENGTH = iMachineIDByteArray.length
      + I_LONG_BYTE_LENGTH + I_INT_BYTE_LENGTH + I_INT_BYTE_LENGTH;
  }
  private int                iSeedSize       = 0;
  private int[]              iSeedA          = null;
  private int[]              iSeedB          = null;

  public SecureRandom()
  {
    super();
    CreateSeed(11);
  }

  public SecureRandom(int iSeedSize)
  {
    super();
    CreateSeed(iSeedSize);
  }

  public static int GetIteration()
  {
    return ++iUniqueIteration;
  }

  public final byte[] GetUniqueByteArray()
  {
    int i = 0;
    int i1 = 0;
    byte[] iResult = new byte[I_UNIQUE_BYTE_ARRAY_LENGTH];
    long iTimeStamp = System.currentTimeMillis();
    for (; i < iMachineIDByteArray.length; ++i)
    {
      iResult[i1++] = iMachineIDByteArray[i];
    }
    for (i = 0; i < I_LONG_BYTE_LENGTH; ++i)
    {
      iResult[i1++] = (byte) ((iTimeStamp >>> (i << 3)) & 0xFF);
    }
    int iRandom = GetIteration();
    for (i = 0; i < I_INT_BYTE_LENGTH; ++i)
    {
      iResult[i1++] = (byte) ((iRandom >>> (i << 3)) & 0xFF);
    }
    iRandom = GetIteration();
    for (i = 0; i < I_INT_BYTE_LENGTH; ++i)
    {
      iResult[i1++] = (byte) ((iRandom >>> (i << 3)) & 0xFF);
    }
    return iResult;
  }

  public final void CreateSeed(int iiSeedSize)
  {
    iSeedSize = Limited(iiSeedSize, I_SEED_SIZE_MIN, I_SEED_SIZE_MAX);
    iSeedA = new int[iSeedSize];
    iSeedB = new int[iSeedSize];
    SetSeed(GetUniqueByteArray());
  }

  public final void SetSolidSeed(Object oInput)
  {
    iScatterGlue = SecureHash.Digest(iSeedA, iSeedB, SecureHash.Digest(oInput,
      iSeedA, 0x5A, true), true);
  }

  public final void SetSeed(Object oInput)
  {
    iScatterGlue = SecureHash.Digest(iSeedA, iSeedB, SecureHash.Digest(oInput,
      iSeedA, iScatterGlue, true), true);
  }

  public final int[] GetSeed()
  {
    RehashSeed();
    return iSeedB;
  }

  public final void RehashSeed()
  {
    iScatterGlue = RehashSeed(++iScatterGlue);
  }

  public final int RehashSeed(int iScatter)
  {
    int[] oPocket;
    int iIteration = ++iSeedA[0] & 0xFF;
    oPocket = iSeedA;
    iSeedA = iSeedB;
    iSeedB = oPocket;
    iSeedA[0] = iIteration;
    return SecureHash.Digest(iSeedA, iSeedB, iScatter, false);
  };

  public final long nextLong()
  {
    long iResult = 0;
    RehashSeed();
    for (int i = 0; i < I_LONG_BYTE_LENGTH; ++i)
    {
      iResult = (long) ((iResult << 8) | iSeedB[i]);
    }
    return iResult;
  }

  public final int nextInt()
  {
    int iResult = 0;
    RehashSeed();
    for (int i = 0; i < I_INT_BYTE_LENGTH; ++i)
    {
      iResult = (int) ((iResult << 8) | iSeedB[i]);
    }
    return iResult;
  }

  public final short nextShort()
  {
    short iResult = 0;
    RehashSeed();
    for (int i = 0; i < I_SHORT_BYTE_LENGTH; ++i)
    {
      iResult = (short) ((iResult << 8) | iSeedB[i]);
    }
    return iResult;
  }

  public final byte nextByte()
  {
    RehashSeed();
    return (byte) iSeedB[iScatterGlue % I_LONG_BYTE_LENGTH];
  }

  public final long nextPositiveLong()
  {
    long iResult = nextLong();
    return (iResult < 0) ? -iResult : iResult;
  }

  public final int nextPositiveInt()
  {
    int iResult = nextInt();
    return (iResult < 0) ? -iResult : iResult;
  }

  public final short nextPositiveShort()
  {
    short iResult = nextShort();
    return (iResult < 0) ? (short) (-iResult) : iResult;
  }

  public final byte nextPositiveByte()
  {
    byte iResult = nextByte();
    return (iResult < 0) ? (byte) (-iResult) : iResult;
  }

  public final long nextLongInRange(long iMin, long iMax)
  {
    return nextPositiveLong() % iMax + iMin;
  }

  public final int nextIntInRange(int iMin, int iMax)
  {
    return nextPositiveInt() % iMax + iMin;
  }

  public final short nextShortInRange(short iMin, short iMax)
  {
    return (short) (nextPositiveShort() % iMax + iMin);
  }

  public final byte nextByteInRange(byte iMin, byte iMax)
  {
    return (byte) (nextPositiveByte() % iMax + iMin);
  }
}